/*
 * pong.c
 *
 * Pong game routines.
 *
 * Copyright (c) 2010, Alan Green.
 * Contactable at <firstname>.<lastname>@gmail.com
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1.  Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *  2.  Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE.
 *
 * ===============================================
 */
#include <stdio.h>
#include <avr/pgmspace.h>
#include "display.h"
#include "util.h"
#include "pong.h"
#include "text_paint.h"

// data
static uint16_t mid_vals[] PROGMEM = { 0xf800, 0x7c00, 0x3e00, 0x1f00, 0x0f80,
        0x07c0, 0x03e0, 0x01f0 };
static uint16_t topbot_vals[] PROGMEM = { 0x7000, 0x3800, 0x1c00, 0x0e00,
        0x0700, 0x0380, 0x01c0, 0x00e0 };

typedef enum {
    P1_SERVE, P2_SERVE, IN_PLAY, OVER
} State;
static State state;
static uint8_t p1_score;
static uint8_t p2_score;
static uint8_t paddle1_pos;
static uint8_t paddle2_pos;
static int ballx, bally;
static int balldx, balldy;
static int ball_sprite_cell;
static uint8_t ball_sprite[5][2];

static char score_fmt[] PROGMEM = "%4d  %-4d";
static char score_buf[11];

// The pong screen consists of:
// 0-23 Score digits
// 24-25 blank
// 26-27 line for top of playing area
// 28-227 playing area (200 lines)
// 228-229 line for bottom of playing area

#define PLAY_HEIGHT 200
#define PLAY_WIDTH 320
#define PADDLE_LEN 20
#define PADDLE_END_SHAPE 0b00011000;
#define PADDLE_MID_SHAPE 0b00111100;
#define MAX_SCORE 5

// Prototypes for line functions
static void score_area_line_function(uint8_t line); // line < 26
static void boundary_line_function(uint8_t line); // line = 26,27 or 228, 229
static void play_area_line_function(uint8_t line); // 28 <= line < 228

// Score area
// precondition: line < 26
static void score_area_line_function(uint8_t line) {

    if (line >= 10 && line < 18) {
        paint_chars(score_buf, 15, line - 10); // assumes pre-cleared
    } else {
        fill_display_ram(0x00);
    }

    if (line == 25) {
        // next line will be boundary
        line_logic = boundary_line_function;
    }
}

// line = 26,27 or 228,289
static void boundary_line_function(uint8_t line) {
    fill_display_ram(0xff);
    if (line == 27) {
        line_logic = play_area_line_function;
    }
}

// line 28 <= line < 228
static void play_area_line_function(uint8_t line) {
    uint8_t pline = line - 28;
    fill_display_ram(0x00);

    // Draw line down the middle
    if ((pline) & 0x08) {
        display_ram[19] = 0x01;
        display_ram[20] = 0x80;
    }

    // Draw paddles
    int p1_diff = pline - paddle1_pos;
    if (p1_diff == 0 || p1_diff == PADDLE_LEN - 1) {
        display_ram[0] = PADDLE_END_SHAPE;
    } else if (p1_diff > 0 && p1_diff < PADDLE_LEN - 1) {
        display_ram[0] = PADDLE_MID_SHAPE;
    }
    int p2_diff = pline - paddle2_pos;
    if (p2_diff == 0 || p2_diff == PADDLE_LEN - 1) {
        display_ram[39] = PADDLE_END_SHAPE;
    } else if (p2_diff > 0 && p2_diff < PADDLE_LEN - 1) {
        display_ram[39] = PADDLE_MID_SHAPE;
    }

    // Layer in the ball
    if (pline >= bally - 2 && pline <= bally + 2) {
        display_ram[ball_sprite_cell] |= ball_sprite[pline - bally + 2][0];
        display_ram[ball_sprite_cell + 1] |= ball_sprite[pline - bally + 2][1];
    }

    // Until end of area
    if (line == 227) {
        line_logic = boundary_line_function;
    }
}

static inline uint8_t transform_pot(uint16_t pot_val, uint8_t max_val) {
    const int pot_min = 100;
    pot_val = ((pot_val < pot_min) ? 0 : pot_val - pot_min) >> 2;
    return pot_val < max_val ? pot_val : max_val;
}

// Reads pots and translates into paddle position
static void read_paddles(void) {
    paddle1_pos = transform_pot(read_pot(PORT5), PLAY_HEIGHT - PADDLE_LEN);
    paddle2_pos = transform_pot(read_pot(PORT5), PLAY_HEIGHT - PADDLE_LEN);
}

// Main pong function
// Executed every 20ms. Has about 84k cycles to complete
void pong_game_function(void) {
    tone_tick();

    uint8_t button1_pressed = !(PINC & _BV(PC0));
    uint8_t button2_pressed = !(PINC & _BV(PC0)); // same as 1

    // Read paddle positions
    read_paddles();

    if (state == P1_SERVE) {
        bally = paddle1_pos + PADDLE_LEN / 2 - 1;
        ballx = 8;
        if (button1_pressed) {
            state = IN_PLAY;
            balldy = -2;
            balldx = 3;
            tone_start(20, 10);
        }
    } else if (state == P2_SERVE) {
        bally = paddle2_pos + PADDLE_LEN / 2 - 1;
        ballx = PLAY_WIDTH - 8 - 1;
        if (button2_pressed) {
            state = IN_PLAY;
            balldy = 2;
            balldx = -3;
        }
    } else if (state == OVER) {
        // Wait for button press
        // if pressed, set state = serving player to loser
        // zero scores
        ballx = bally = -100;
        if (button1_pressed && button2_pressed) {
            state = (p1_score > p2_score) ? P2_SERVE : P1_SERVE;
            p1_score = p2_score = 0;
        }
    } else if (state == IN_PLAY) {
        int newbally = bally + balldy;

        // Bounce top and bottom
        if (newbally < 1 || newbally > PLAY_HEIGHT - 2) {
            bally = (newbally + bally) / 2;
            balldy = -balldy;
        } else {
            bally = newbally;
        }

        // paddles / ends
        ballx = ballx + balldx;
        if (ballx < 0) {
            p2_score += 1;
            state = P1_SERVE;
        } else if (ballx >= PLAY_WIDTH) {
            p1_score += 1;
            state = P2_SERVE;
        } else if (ballx < 8 && balldx < 0) {
            // hit it back
            if (bally >= paddle1_pos - 2 && bally < paddle1_pos + PADDLE_LEN
                    + 2) {
                balldx = -balldx;
            }
        } else if (ballx > (PLAY_WIDTH - 9) && balldx > 0) {
            // hit it back
            if (bally >= paddle2_pos - 2 && bally < paddle2_pos + PADDLE_LEN
                    + 2) {
                balldx = -balldx;
            }
        }

        if (p1_score > MAX_SCORE || p2_score > MAX_SCORE) {
            state = OVER;
        }
    }

    // Calculate ball display bytes + position
    int ball_left = ballx - 2;
    ball_sprite_cell = ball_left >> 3;

    uint16_t topbot = pgm_read_word(&(topbot_vals[ball_left & 0x7]));
    uint16_t mid = pgm_read_word(&(mid_vals[ball_left & 0x7]));
    ball_sprite[0][0] = topbot >> 8;
    ball_sprite[0][1] = topbot & 0xff;
    ball_sprite[1][0] = mid >> 8;
    ball_sprite[1][1] = mid & 0xff;
    ball_sprite[2][0] = mid >> 8;
    ball_sprite[2][1] = mid & 0xff;
    ball_sprite[3][0] = mid >> 8;
    ball_sprite[3][1] = mid & 0xff;
    ball_sprite[4][0] = topbot >> 8;
    ball_sprite[4][1] = topbot & 0xff;

    snprintf_P(score_buf, 11, score_fmt, p1_score, p2_score);
    line_logic = score_area_line_function;
}

void pong_init(void) {
    frame_logic = pong_game_function;
    state = P1_SERVE;
}
